package dados;

import java.util.ArrayList;
import java.util.List;
import javax.faces.context.FacesContext;
import javax.naming.InitialContext;
import org.hibernate.Criteria;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.classic.Session;
import org.hibernate.criterion.Restrictions;

import business.sistema.Properties;

public class Persistente
{
	private static Persistente objBanco = null;
	private static boolean web;
	
	/**
	 * Construtor privado da classe. Para ter acesso a uma instância da classe Persistente
	 * deve-se recupera-la através de um dos métodos:<br><br>    
	 * 
	 * getInstanceWEB() - para sessões que rodam no container web <br>
	 * getInstanceDesktop() - para sessões que rodam localmente
	 * 
	 * @throws Exception
	 */
	private Persistente() throws Exception
	{		
		Configuration config = new Configuration();
		
		if(isWeb())
		{
			config.configure().buildSessionFactory();
		}
		else
		{
			/* Propriedades */
			config.setProperty("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
			config.setProperty("hibernate.connection.driver_class", "org.postgresql.Driver");
			config.setProperty("hibernate.connection.url", Properties.URL());
			config.setProperty("hibernate.connection.username", "postgres");
			config.setProperty("hibernate.connection.password", "pos73dda");
			config.setProperty("hibernate.transaction.auto_close_session", "false");
			config.setProperty("hibernate.show_sql", "true");
			config.setProperty("hibernate.format_sql", "true");
			config.setProperty("hibernate.use_sql_comments", "true");
			config.setProperty("hibernate.connection.autocommit", "false");
			
			/* Mapeamentos */		
			for(String mapeamento : Properties.MAPEAMENTO())
			{
				config.addResource(mapeamento);
			}
			
			config.buildSessionFactory();
		}
	}
	
	public static boolean isWeb()
	{
		return web;
	}
	
	
	/**
	 * Método responsável por recuperar o objeto que realiza as operações com o banco de dados.
	 * 
	 * @return objBanco : Persistente
	 * @throws Exception
	 */
	public static Persistente getInstanceWEB() throws Exception
	{
		web = true;
		
		if (objBanco == null)
		{
			objBanco = new Persistente();
		}
		
		return objBanco;
	}
	
	/**
	 * Método responsável por recuperar o objeto que realiza as operações com o banco de dados.
	 * 
	 * @return objBanco : Persistente
	 * @throws Exception
	 */
	public static Persistente getInstanceDesktop() throws Exception
	{
		web = false;
		
		if (objBanco == null)
		{
			objBanco = new Persistente();
		}
		
		return objBanco;
	}
	
	/**
	 * Método responsável por recuperar uma sessão
	 * 
	 * @return session : Session
	 */
	private Session getSession() throws Exception
	{	
//		UserTransaction userTransaction = (UserTransaction)new InitialContext().lookup("UserTransaction");
//		if(userTransaction.getStatus() == Status.STATUS_NO_TRANSACTION)
//		{
//			userTransaction.begin();
//			userTransaction.setTransactionTimeout(300);
//		}

		Session session = (Session)FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("hibernateSession");
		
		if(session == null || !session.isOpen())
		{
			SessionFactory sessionFactory = (SessionFactory) new InitialContext().lookup("sessionFactoryPostgres");
			session = sessionFactory.openSession();
			FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("hibernateSession", session);
		}
		
		return session;
	}
	
	/**
	 * Método responsável por salvar objetos individuais no banco
	 * 
	 * @param obj Objeto que será salvo no banco
	 */
	public void save(Object objeto) throws Exception
	{	
		Session session = null;
		Transaction tx = null;
		try
		{
			session = getSession();
			tx = session.beginTransaction();
			session.saveOrUpdate(objeto);			
			tx.commit();
		}
		catch(Exception ex)
		{
			tx.rollback();
			desfazTransacaoUsuario();
			throw new Exception(ex);
		}
	}	
	
	/**
	 * Método para demarcação de commit de transação de usuário.
	 * 
	 * @throws Exception
	 */
	public void finalizaTransacaoUsuario() throws Exception
	{
//		UserTransaction userTransaction = (UserTransaction)new InitialContext().lookup("UserTransaction");
//		userTransaction.commit();
	}
	
	/**
	 * Método para demarcação para desfazer a transação de usuário.
	 * 
	 * @throws Exception
	 */
	public void desfazTransacaoUsuario() throws Exception
	{
//		UserTransaction userTransaction = (UserTransaction)new InitialContext().lookup("UserTransaction");
//		userTransaction.rollback();
	}
	
	/**
	 * Método responsável por remover da sessão as alterações realizadas em um objeto.
	 * 
	 * @param objeto : Object
	 * @throws Exception
	 */
	public void desfazerModificacao(Object objeto) throws Exception
	{
		Session session = null;
		try
		{
			session = getSession();
			session.evict(objeto);
			session.clear();
		}
		catch(Exception ex)
		{
			throw new Exception(ex);
		}
	}
	
	/**
	 * Método responsável por remover objeto do banco
	 * 
	 * @param object Objeto que será removido do banco
	 */
	public void remove(Object objeto) throws Exception
	{
		Session session = null;
		Transaction tx = null;
		try
		{
			session = getSession();
			tx = session.beginTransaction();
			session.delete(objeto);
			tx.commit();
		}
		catch(Exception ex)
		{
			tx.rollback();
			throw new Exception(ex);
		}
	}
	
	/**
	 * Método responsável por recuperar uma coleção de objetos do banco.
	 * 
	 * @param classe : Class<?> - Classe da coleção de objetos.
	 * @param sql : String - critério de busca.
	 * @return lista : List<Object> - Lista com os objetos.
	 */
	public List<?> getCollection(Class<?> classe, String sql) throws Exception
	{		
		List<?> lista = new ArrayList<Object>();
		
		Session session = getSession();
			
		Criteria criterio = session.createCriteria(classe);
		criterio.add(Restrictions.sqlRestriction(sql));
		lista = criterio.list();

		return lista;
	}
	
	/**
	 * Classe responsável por retornar a coleção de objetos por consulta em HQL.
	 * 
	 * @param classe
	 * @param outrasTabelas
	 * @param restricoes
	 * @return
	 * @throws Exception
	 */
	public List<?> getCollection(Class<?> classe, List<String[]> outrasTabelas, String filtro) throws Exception
	{		
		List<?> lista = new ArrayList<Object>();
		Session session = getSession();
		
		Criteria criterio = session.createCriteria(classe);
			
		for(String[] tabela : outrasTabelas)
		{
			if(tabela.length == 2)
			{
				criterio.createCriteria(tabela[0], tabela[1]);
			}
		}
			
		criterio.add(Restrictions.sqlRestriction(filtro));
		lista = criterio.list();
		
		return lista;
	}
	
	/**
	 * Método responsável por recuperar um objeto do banco
	 * 
	 * @param classe : Class<?> - classe do objeto a ser recuperado
	 * @param identificador : int - identificador do objeto
	 * @return objetoRecuperado : Object - com os dados pertencentes ao objeto
	 */
	public Object getObject(Class<?> classe, int identificador) throws Exception
	{
		Object objetoRecuperado = null;
		Session session = getSession();
		
		objetoRecuperado = session.get(classe, new Integer(identificador));
		
		return objetoRecuperado;
	}

	/**
	 * Método responsável colocar um objeto detached na seção.
	 * 
	 * @param objeto : Object
	 * @throws Exception
	 */
	public Object mergeSession(Object objeto) throws Exception
	{
		Session session = getSession();
		
		return session.merge(objeto);
	}

	/**
	 * Método responsável por atualizar um objeto com a sessão.
	 * 
	 * @param objeto : Object
	 * @throws Exception
	 */
	public void update(Object objeto) throws Exception
	{
		Session session = null;
		Transaction tx = null;
		try
		{
			session = getSession();
			tx = session.beginTransaction();
			
			session.update(objeto);
			
			tx.commit();
		}
		catch(Exception ex)
		{
			tx.rollback();
			throw new Exception(ex);
		}
	}
}
